package lexer;

import java.io.*;
import java.util.ArrayList;

import token.ReservedWords;
import token.Token;
import token.Token.TokenType;


public class Lexer {

	private Reader reader;
	private int actualChar;
	private int remainingChar;
	private int	tokenNo,lineNo;
	private Scope constantIdentifiers,typeIdentifiers,procedureIdentifiers,functionIdentifiers,identifiers,variables; 

	public Lexer(String textToAnalize) {
		reader = new StringReader(textToAnalize);
		tokenNo = 0;
		lineNo = 0;
		remainingChar=-2; //minus 2 implies that there is not a character remaining in the queue
		constantIdentifiers =  new Scope();
		typeIdentifiers =  new Scope();
		procedureIdentifiers = new Scope();
		identifiers = new Scope();
		functionIdentifiers = new Scope();
		variables = new Scope();
		System.out.println("Reading from String");
	}

	public Lexer(File source) throws FileNotFoundException {
		reader = new FileReader(source);
		remainingChar=-2; //minus 2 implies that there is not a character remaining in the queue
		constantIdentifiers =  new Scope();
		typeIdentifiers =  new Scope();
		procedureIdentifiers = new Scope();
		identifiers = new Scope();
		functionIdentifiers = new Scope();
		variables = new Scope();
		tokenNo = 0;
		lineNo = 0;
		System.out.println("Reading from file\n\tFile: "+source.getAbsolutePath());
	}

	public Token nextToken() throws IOException {	//TODO: add an exception to throw in case of an string without a close quotation mark
		tokenNo++;
		for (actualChar = (remainingChar!=-2)?remainingChar:reader.read(); actualChar != -1; actualChar = reader.read()) {
			if (remainingChar!=-2) {
				remainingChar=-2;
			}
			if (actualChar == '\n') {
				lineNo++;
				tokenNo = 0;
				continue;
			}
			switch ((char)actualChar) {
			case '(':
				return Token.buildO_PAREN();
			case ')':
				return Token.buildC_PAREN();
			case '{':
				return Token.buildO_CURBRACKET();
			case '}':
				return Token.buildC_CURBRACKET();
			case '[':
				return Token.buildO_SQBRACKET();
			case ']':
				return Token.buildC_SQBRACKET();
			case '+':
				return Token.buildPLUS_SIGN();
			case '-':
				return Token.buildMINUS_SIGN();
			case '=':
				return Token.buildEQUALS_SIGN();
			case '<':
				return complexRelationalOperator('<');
			case '>':
				return complexRelationalOperator('>');
			case '*':
				return Token.buildMULTIPLICATION_OPERATOR();
			case '/':
				return Token.buildDIVISION_OPERATOR();
			case ';':
				return Token.buildSEMICOLON();
			case ',':
				return Token.buildCOMMA();
			case '.':
				actualChar = reader.read();
				if (actualChar == '.') {
					 return Token.buildDOUBLE_DOT();
				}
				remainingChar = actualChar;
				return Token.buildDOT();
			case ':':
				actualChar = reader.read();
				if (actualChar == '=') {
					 return Token.buildCOLON_EQUALS();
				}
				remainingChar = actualChar;
				return Token.buildCOLON();
			case '^':
				return Token.buildPOINTER_TYPE();
			}
			if(actualChar == '\'') {	//STRING
				return Token.buildString(readString());
			}else if(isDigit(actualChar)) { //DIGIT
				return Token.buildUNSIGNED_DIGIT_SEQUENCE(readUnsignedDigitSequence());
			}else if(isLetter(actualChar)) { //IDENTIFIER
				String identifierString = readIdentifier();
				if(isAScaleFactor(identifierString)){
					return Token.buildSCALE_FACTOR(identifierString);
				}
				Token tokenToReturn = ReservedWords.isAReservedWord(identifierString);
				if (tokenToReturn == null) {
					tokenToReturn = constantIdentifiers.isInThisScope(identifierString);
				}
				if (tokenToReturn == null) {
					tokenToReturn = typeIdentifiers.isInThisScope(identifierString);
				}
				if (tokenToReturn == null) {
					tokenToReturn = procedureIdentifiers.isInThisScope(identifierString);
				}
				if (tokenToReturn == null) {
					tokenToReturn = identifiers.isInThisScope(identifierString);
				}
				if (tokenToReturn == null) {
					tokenToReturn = variables.isInThisScope(identifierString);
				}
				return (tokenToReturn != null)?tokenToReturn:Token.buildIDENTIFIER(identifierString);
			}
		}
		return Token.buildEOF();
	}

	private boolean isLetter(int asciiCode) {
		return ((asciiCode > 64 && asciiCode < 91) || (asciiCode > 96 && asciiCode < 123))?true:false;
	}

	private boolean isDigit(int asciiCode) {
		return (asciiCode > 47 && asciiCode < 58)?true:false;
	}

	private String readString() throws IOException {  //STRING
		StringBuilder protoToken = new StringBuilder();
		do {
			if (actualChar == '\n') {
				lineNo++;
				tokenNo = 0;
				actualChar = reader.read();
				continue;
			}
			protoToken.append((char)actualChar);
			actualChar = reader.read();
		}while(actualChar != '\'' && actualChar != -1);
		if(actualChar != -1) {
			protoToken.append('\'');
		}else {		//THIS WILL(MAY) TROW AN EXCEPTION IN CASE OF A NOT PAIR NUMBER OF QUOTATION MARKS
			remainingChar = -1;
			actualChar = -1; //IF USER TRY TO READ ANOTHER TOKEN IT WILL BE IMPOSSIBLE
			System.err.println("\nA quotation mark is opened but never closed\n");
		}
		return protoToken.toString();
	}

	private String readUnsignedDigitSequence() throws IOException {	//UNSIGNED DIGIT SEQUENCE
		StringBuilder protoToken = new StringBuilder();
		do {
			if (actualChar == '\n') {
				lineNo++;
				tokenNo = 0;
				actualChar = reader.read();
				continue;
			}
			protoToken.append((char)actualChar);
			actualChar = reader.read();
		}while(isDigit(actualChar) && actualChar != -1);
		remainingChar=actualChar;
		return protoToken.toString();
	}

	private String readIdentifier() throws IOException {	//IDENTIFIER
		StringBuilder protoToken = new StringBuilder();
		if (actualChar == 'e') {	//this is in case it is a scaleFactor whit a sign immediately next
			protoToken.append((char)actualChar);
			actualChar = reader.read();
			if (actualChar == '+' || actualChar == '-') {
				protoToken.append((char)actualChar);
				actualChar = reader.read();
			}
			if(actualChar == -1 || !(isDigit(actualChar) || isLetter(actualChar))){
				return protoToken.toString();
			}
		}
		do {
			if (actualChar == '\n') {
				lineNo++;
				tokenNo = 0;
				actualChar = reader.read();
				continue;
			}
			protoToken.append((char)actualChar);
			actualChar = reader.read();
		}while((isDigit(actualChar) || isLetter(actualChar)) && actualChar != -1);
		remainingChar=actualChar;
		return protoToken.toString();
	}

	private boolean isAScaleFactor(String identifier) {
		int i = 1;
		if(identifier.charAt(0) != 'e') {
			return false;
		}
		if(identifier.charAt(1) == '-' || identifier.charAt(1) == '+') {
			i++;
		}
		int length = identifier.length();
		for(;i<length;i++) {
			if(!isDigit(identifier.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	private Token complexRelationalOperator(int beginOfComplexRO) throws IOException {
		if(beginOfComplexRO == '<') {
			actualChar = reader.read();
			if(actualChar == '>') {
				return Token.buildDISTINCT();
			}else if(actualChar == '=') {
				return Token.buildLESSEQUALS_THAN();
			}else {
				remainingChar=actualChar;
				return Token.buildLESS_THAN();
			}
		}else if(beginOfComplexRO == '>') {
			actualChar = reader.read();
			if(actualChar == '=') {
				return Token.buildGREATEREQUALS_THAN();
			}else {
				remainingChar=actualChar;
				return Token.buildGREATER_THAN();
			}
		}
		remainingChar=actualChar;
		System.err.println("The initial character given to the method complexRelational_Operator wasn't either < OR >");
		return null;
	}

	public int tokenRead() {
		return tokenNo;
	}
	
	public int lineRead() {
		return lineNo;
	}

	public void addToConstants(String key,Token value) {
		constantIdentifiers.put(key, Token.buildCONSTANT_IDENTIFIER(value.getValue()));
	}

	public void addToTypes(String key,Token value) {
		typeIdentifiers.put(key, Token.buildTYPE_IDENTIFIER(value.getValue()));
	}
	
	public void addToProcedures(String key,Token value) {
		procedureIdentifiers.put(key, Token.buildPROCEDURE_IDENTIFIER(value.getValue()));
	}
	
	public void addToFunctions(String key,Token value) {
		functionIdentifiers.put(key, Token.buildFUNCTION_IDENTIFIER(value.getValue()));
	}
	
	public void addToVariables(ArrayList<Token> variableIdentifiers,Token typeOfVariable) {		//All of this are TokenType.TYPE_IDENTIFIER
		for (Token tk : variableIdentifiers) {
			addToVariables(tk,typeOfVariable);
		}
	}
	
	public void addToVariables(Token variableIdentifier,Token typeOfVariable) {		//All of this are TokenType.TYPE_IDENTIFIER
		variables.put(variableIdentifier.getValue(), Token.buildTYPE_VARIABLE(variableIdentifier.getValue(), typeOfVariable));
	}
	
	public void addToVariables(String key,Token value,TokenType typeOfToken) {		//TODO: Posiblemente quitar
		if(typeOfToken == TokenType.FILE_VARIABLE)
		{
			variables.put(key, Token.buildFILE_VARIABLE(value.getValue()));
		}
		
		if(typeOfToken == TokenType.RECORD_VARIABLE)
		{
			variables.put(key, Token.buildRECORD_VARIABLE(value.getValue()));
		}
		
		if(typeOfToken == TokenType.POINTER_VARIABLE)
		{
			variables.put(key, Token.buildPOINTER_VARIABLE(value.getValue()));
		}
		
		if(typeOfToken == TokenType.ACTUAL_VARIABLE)
		{
			variables.put(key, Token.buildACTUAL_VARIABLE(value.getValue()));
		}
		
		if(typeOfToken == TokenType.ARRAY_VARIABLE)
		{
			variables.put(key, Token.buildARRAY_VARIABLE(value.getValue()));
		}
	}
	
	public void addToIdentifiers(ArrayList<Token> identifiers,TokenType typeOfToken) {
		for (Token tk : identifiers) {
			this.addToIdentifiers(tk, typeOfToken);
		}
	}
	
	public void addToIdentifiers(Token value,TokenType typeOfToken) {
		if(typeOfToken == TokenType.FIELD_IDENTIFIER)
		{
			identifiers.put(value.getValue(), Token.buildFIELD_IDENTIFIER(value.getValue()));
		}
		
		if(typeOfToken == TokenType.CONSTANT_IDENTIFIER)
		{
			identifiers.put(value.getValue(), Token.buildCONSTANT_IDENTIFIER(value.getValue()));
		}
		
		if(typeOfToken == TokenType.CONSTANT_IDENTIFIER)
		{
			identifiers.put(value.getValue(), Token.buildCONSTANT_IDENTIFIER(value.getValue()));
		}
		
		if(typeOfToken == TokenType.VARIABLE_IDENTIFIER)
		{
			identifiers.put(value.getValue(), Token.buildVARIABLE_IDENTIFIER(value.getValue()));
		}
		
		if(typeOfToken == TokenType.TYPE_IDENTIFIER)
		{
			identifiers.put(value.getValue(), Token.buildTYPE_IDENTIFIER(value.getValue()));
		}
		
		if(typeOfToken == TokenType.PROCEDURE_IDENTIFIER)
		{
			identifiers.put(value.getValue(), Token.buildPROCEDURE_IDENTIFIER(value.getValue()));
		}
		
		if(typeOfToken == TokenType.FUNCTION_IDENTIFIER)
		{
			identifiers.put(value.getValue(), Token.buildFUNCTION_IDENTIFIER(value.getValue()));
		}
		
		if(typeOfToken == TokenType.BOUND_IDENTIFIER)
		{
			identifiers.put(value.getValue(), Token.buildBOUND_IDENTIFIER(value.getValue()));
		}
	}
	
	public boolean isVariable(String key) {
		return (variables.isInThisScope(key) != null)?true:false;
	}

}
